package com.jd.opendj.api.util;

import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpRequest;
import org.apache.http.NoHttpResponseException;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.protocol.HttpContext;
import org.apache.http.ssl.SSLContexts;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;

/**
 * Created by tcw on 2017/6/15.
 * @author tcw
 * TODO 参数可配置
 */
public class HttpClientPool
{
    private static final HttpClientPool httpClientPool = new HttpClientPool();
    private static final int REQUEST_TIMEOUT = 10000;
    private static final int CONNECT_TIMEOUT = 20000;
    private static final int SOCKET_TIMEOUT = 10000;
    private static final int MAX_CONNECT = 600;
    private static final int MAX_PER_ROUTE = 20;
    private static final int DEFAULT_MAX_RETRY = 5;
    private PoolingHttpClientConnectionManager connectionManager;
    private HttpRequestRetryHandler httpRequestRetryHandler;

    private HttpClientPool() {
        try {
            SSLContext sslcontext = SSLContexts.custom().loadTrustMaterial(null,
                    new TrustSelfSignedStrategy())
                    .build();
            SSLConnectionSocketFactory sslSF = new SSLConnectionSocketFactory(
                    sslcontext, NoopHostnameVerifier.INSTANCE);
            Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
                    .register("http", PlainConnectionSocketFactory.getSocketFactory())
                    .register("https", sslSF)
                    .build();
            connectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
            connectionManager.setMaxTotal(MAX_CONNECT);
            connectionManager.setDefaultMaxPerRoute(MAX_PER_ROUTE);
            httpRequestRetryHandler = new RetryHandler();
        } catch (NoSuchAlgorithmException | KeyStoreException | KeyManagementException e) {
            e.printStackTrace();
        }
    }

    public static CloseableHttpResponse execute(final HttpUriRequest request) throws IOException, ClientProtocolException {
        return httpClientPool.getHttpClient().execute(request);
    }

    public CloseableHttpClient getHttpClient() {
        return getHttpClient(REQUEST_TIMEOUT, CONNECT_TIMEOUT, SOCKET_TIMEOUT, false, null);
    }

    public synchronized CloseableHttpClient getHttpClient(int requestTimeout, int connectTimeout, int socketTimeout, boolean isRetry, HttpRequestRetryHandler retryHandler) {
        RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(requestTimeout)
                .setConnectTimeout(connectTimeout).setSocketTimeout(socketTimeout).build();
        CloseableHttpClient client = null;
        if (isRetry) {
            if (retryHandler != null) {
                client = HttpClients.custom()
                        .setConnectionManager(connectionManager).setDefaultRequestConfig(requestConfig).setRetryHandler(retryHandler).build();
            } else {
                client = HttpClients.custom()
                        .setConnectionManager(connectionManager).setDefaultRequestConfig(requestConfig).setRetryHandler(httpRequestRetryHandler).build();
            }
        } else {
            client = HttpClients.custom()
                    .setConnectionManager(connectionManager).setDefaultRequestConfig(requestConfig).build();
        }
        return client;
    }

    public static class RetryHandler implements HttpRequestRetryHandler {
        @Override
        public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
            // 如果已经重试了5次，就放弃
            if (executionCount >= DEFAULT_MAX_RETRY) {
                return false;
            }
            // 如果服务器丢掉了连接，那么就重试
            if (exception instanceof NoHttpResponseException) {
                return true;
            }
            // 不要重试SSL握手异常
            if (exception instanceof SSLHandshakeException) {
                return false;
            }
            // 超时
            if (exception instanceof InterruptedIOException) {
                return false;
            }
            // 目标服务器不可达
            if (exception instanceof UnknownHostException) {
                return false;
            }
            // ssl握手异常
            if (exception instanceof SSLException) {
                return false;
            }
            HttpClientContext clientContext = HttpClientContext.adapt(context);
            HttpRequest request = clientContext.getRequest();
            // 如果请求是幂等的，就再次尝试
            return !(request instanceof HttpEntityEnclosingRequest);
        }
    }
}
